﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using System.Windows;
using System.Windows.Interop;
using Microsoft.Surface.Core;

namespace Black.SurfaceToolkit.Tools
{
    /// <summary>
    /// Gère le suivi des contacts.
    /// </summary>
    public class ContactTracking
    {
        #region Evénements

        /// <summary>
        /// Se produit lorsqu'un balayage des contacts démarre.
        /// </summary>
        public event EventHandler<ContactTrackingEventArgs> ScanStarted;
        /// <summary>
        /// Se produit lorsqu'un balayage des contacts se termine.
        /// </summary>
        public event EventHandler<ContactTrackingEventArgs> ScanCompleted;

        #endregion

        #region Accesseurs

        /// <summary>
        /// Obtient la liste des contacts nouvellement détectés depuis le dernier balayage.
        /// </summary>
        public List<CoreContact> ContactNew
        {
            get
            {
                return (this.m_oContactsNew);
            }
        }

        /// <summary>
        /// Obtient la liste des contacts toujours en détection depuis le dernier balayage.
        /// </summary>
        public List<CoreContact> ContactUpdate
        {
            get
            {
                return (this.m_oContactsNew);
            }
        }

        /// <summary>
        /// Obtient la liste des contacts supprimés depuis le dernier balayage.
        /// </summary>
        public List<CoreContact> ContactRemove
        {
            get
            {
                return (this.m_oContactsNew);
            }
        }

        /// <summary>
        /// Obtient ou définit l'interval de temps entre chaque balayage des contacts.
        /// </summary>
        public double ScanInterval
        {
            get
            {
                return (this.m_oTimer.Interval);
            }
            set
            {
                this.m_oTimer.Interval = value;
            }
        }

        #endregion

        #region Variables d'instance

        private bool m_bScanProgress;
        private Timer m_oTimer;
        private TouchTarget m_oContactTarget;
        private List<CoreContact> m_oContactsNew;
        private List<CoreContact> m_oContactsUpdate;
        private List<CoreContact> m_oContactsOld;

        #endregion

        #region Constructeurs

        /// <summary>
        /// Initialise une nouvelle instance de <see cref="T:Black.SurfaceToolkit.Tools.ContactTracking" />.
        /// </summary>
        /// <param name="p_oContext">Elément dans la fenêtre à surveiller.</param>
        public ContactTracking(FrameworkElement p_oContext)
            : this(ContactTracking.GetWindow(p_oContext))
        {
        }

        /// <summary>
        /// Initialise une nouvelle instance de <see cref="T:Black.SurfaceToolkit.Tools.ContactTracking" />.
        /// </summary>
        /// <param name="p_oContext">Fenêtre à surveiller.</param>
        public ContactTracking(Window p_oContext)
            : this(new WindowInteropHelper(p_oContext).Handle)
        {
        }

        /// <summary>
        /// Initialise une nouvelle instance de <see cref="T:Black.SurfaceToolkit.Tools.ContactTracking" />.
        /// </summary>
        /// <param name="p_oScanHandle">Handle de la fenêtre à surveiller.</param>
        public ContactTracking(IntPtr p_oScanHandle)
            : base()
        {
            // Création du timer

            this.m_oTimer = new Timer();
            this.m_oTimer.AutoReset = true;
            this.m_oTimer.Interval = 100;
            this.m_oTimer.Elapsed += new ElapsedEventHandler(Timer_Elapsed);

            // Création du scrutateur

            this.m_oContactTarget = new TouchTarget(p_oScanHandle, EventThreadChoice.OnCurrentThread);
            this.m_oContactTarget.EnableInput();

            // Création des collections de scrutation

            this.m_oContactsNew = new List<CoreContact>();
            this.m_oContactsOld = new List<CoreContact>();
            this.m_oContactsUpdate = new List<CoreContact>();
        }

        #endregion

        #region Evénements XAML

        /// <summary>
        /// Appelée sur le tick du timer pour le scan.
        /// </summary>
        /// <param name="p_oSender">Source de l'appel.</param>
        /// <param name="p_oArgs">Argument de l'appel.</param>
        private void Timer_Elapsed(object p_oSender, ElapsedEventArgs p_oArgs)
        {
            // Vérification scan en cours

            if (this.m_bScanProgress)
                return;

            // Scan en cours

            this.m_bScanProgress = true;

            try
            {
                // Avertissement utilisateur sur le début

                if (this.ScanStarted != null)
                    this.ScanStarted(this, new ContactTrackingEventArgs(this.m_oContactsNew, this.m_oContactsUpdate, this.m_oContactsOld));

                // On scan

                this.ScanContacts();

                // Avertissement utilisateur sur la fin

                if (this.ScanCompleted != null)
                    this.ScanCompleted(this, new ContactTrackingEventArgs(this.m_oContactsNew, this.m_oContactsUpdate, this.m_oContactsOld));
            }
            catch (Exception)
            {
            }

            // Scan terminée

            this.m_bScanProgress = false;
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Obtient une valeur indiquant si le contact spécifié est en vie.
        /// </summary>
        /// <param name="p_iIdContact">Identifiant du contact dont on veut savoir s'il est en vie.</param>
        /// <returns><see langword="true" /> si le contact d'identifiant <paramref name="p_iIdContact" /> est en vie, <see langword="false" /> sinon.</returns>
        public bool IsContactAlive(int p_iIdContact)
        {
            ReadOnlyTouchPointCollection oContacts = this.m_oContactTarget.GetState();

            // Recherche du contact

            TouchPoint oSearch = oContacts.Where(i => i.Id == p_iIdContact).FirstOrDefault();

            // Test d'existance

            return (oSearch != null);
        }

        /// <summary>
        /// Démarre le suivi des contacts.
        /// </summary>
        public void StartTrack()
        {
            this.m_bScanProgress = false;
            this.m_oTimer.Start();
        }

        /// <summary>
        /// Stop le suivi des contacts.
        /// </summary>
        public void StopTrack()
        {
            this.m_oTimer.Stop();
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Effectue un balayage les contacts.
        /// </summary>
        private void ScanContacts()
        {
            // Récupération des contacts

            ReadOnlyTouchPointCollection oContacts = this.m_oContactTarget.GetState();

            // On transfert les contacts

            this.m_oContactsNew.ForEach(i => this.m_oContactsUpdate.Add(i));

            // On vide les listes

            this.m_oContactsNew.Clear();
            this.m_oContactsOld.Clear();

            // On récupère la nouvelle définition des contacts

            List<CoreContact> oNewList = new List<CoreContact>();

            foreach (TouchPoint oContact in oContacts)
            {
                CoreContact oVerif = this.m_oContactsUpdate.Where(i => i.Contact.Id == oContact.Id).FirstOrDefault();

                if (oVerif == null)
                    this.m_oContactsNew.Add(new CoreContact(oContact));
                else
                {
                    CoreContact oContactUpdate = new CoreContact(oContact);

                    // Vérificaiton changement

                    if (oVerif.Contact.X != oContact.X || oVerif.Contact.Y != oContact.Y || oVerif.Contact.Orientation != oContact.Orientation)
                        oContactUpdate.Changed = true;

                    // Ajout à la liste

                    oNewList.Add(oContactUpdate);
                }
            }

            // On check ceux qui n'p_dY sont plus

            TouchPoint oTmp;

            foreach (CoreContact oValue in this.m_oContactsUpdate)
            {
                if (!oContacts.TryGetTouchPointFromId(oValue.Contact.Id, out oTmp))
                {
                    oValue.Changed = false;
                    oValue.IsAlive = false;

                    this.m_oContactsOld.Add(oValue);
                }
            }
            // On switch la liste

            this.m_oContactsUpdate = oNewList;
        }

        #endregion

        #region Fonctions statiques

        /// <summary>
        /// Obtient la <see cref="T:System.Windows.Window" /> du <see cref="T:System.Windows.FrameworkElement" /> spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la <see cref="T:System.Windows.Window" />.</param>
        /// <returns><see cref="T:System.Windows.Window" /> de <paramref name="p_oElement" /> si elle existe, <see langword="null" /> sinon.</returns>
        public static Window GetWindow(FrameworkElement p_oElement)
        {
            FrameworkElement oActual = p_oElement.Parent as FrameworkElement;

            // On boule sur l'arbre

            while (oActual != null)
            {
                // On test si m_dC'est une Window

                if (oActual is Window)
                    return ((Window)oActual);

                // On prend le parent

                oActual = oActual.Parent as FrameworkElement;
            }

            // Non trouvé

            return (null);
        }

        #endregion
    }
}
